using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Media;

using WindowsGame1.Extensions;
using Microsoft.Xna.Framework.Input;

namespace WindowsGame1
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        
        Input _player1 = new Input(PlayerIndex.One);
        Random _rand = new Random();
        bool ballCollision = false;
        bool soundOn = true;
        //TimeSpan ballCollisionCooldown = TimeSpan.FromSeconds(1);
        GraphicsDeviceManager graphics;
        
        SpriteBatch spriteBatch;
        bool drawDebug = false;

        float scrollPosition = 0;
        bool gameStarted = false;

        Vector2 _bluePaddlePosition = new Vector2(0),
                        _ballMinimumSpeed = new Vector2(50),
                        _redPaddlePosition,
                        _ballMenuPosition = new Vector2(0, 0);

        bool _showMenus = true;

        SoundEffect hitEffect,
                            missEffect;
        protected GameObjects.GameObject ball, menuSp,menuExit,menuDungeon,
            menuMp;
        protected Texture2D smhfl;
        protected Texture2D spaceBackground, cityBackground, background, menuTexture, gameTexture;
        protected SpriteFont font;


        protected static readonly Rectangle
            xnaPongLogo = new Rectangle(0, 0, 512, 110),
           

            gameLives = new Rectangle(0, 222, 100, 34),
            gameRedWon = new Rectangle(151, 222, 155, 34),
            gameBlueWon = new Rectangle(338, 222, 165, 34),
            gameRedPaddle = new Rectangle(23, 0, 22, 92),
            gameBluePaddle = new Rectangle(0, 0, 22, 92),
            // gameBall = new Rectangle(1, 94, 33, 33),
            gameSmallBall = new Rectangle(37, 108, 19, 19);

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {

            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            cityBackground = Content.Load<Texture2D>("CityGroundSmall");
            spaceBackground = Content.Load<Texture2D>("SpaceBackground");
            explosionTexture = Content.Load<Texture2D>("explosion");
            smhfl = Content.Load<Texture2D>("smhfl");
            background = spaceBackground;

            gameTexture = Content.Load<Texture2D>("PongGame");
            ball = new GameObjects.GameObject(gameTexture, new Rectangle(1, 94, 33, 33));

            menuTexture = Content.Load<Texture2D>("PongMenu");
            menuSp = new GameObjects.GameObject(menuTexture, new Rectangle(0, 110, 512, 38));
            menuExit = new GameObjects.GameObject(menuTexture, new Rectangle(0, 185, 512, 38));
            menuDungeon = new GameObjects.GameObject(smhfl, new Rectangle(0, 0, 30, 30));
            menuMp = new GameObjects.GameObject(menuTexture, new Rectangle(0, 148, 512, 38));
            hitEffect = Content.Load<SoundEffect>("PongBallHit");
            missEffect = Content.Load<SoundEffect>("PongBallLost");
            font = Content.Load<SpriteFont>("Fire");
            
        }


        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (_player1.Quit)
                this.Exit();
            else
                ProcessUpdates(gameTime);


            base.Update(gameTime);
        }




        private void ProcessUpdates(GameTime gameTime)
        {
            if (this._showMenus)
            {
                ProcessMenus(gameTime);
                return;
            }
            else
            {
                ProcessGame(gameTime);
                return;
            }

        }

        private void ProcessMenus(GameTime gameTime)
        {
            IsMouseVisible = true;
            var mouse = Mouse.GetState();
            if (_player1.Activate ||
                (mouse.LeftButton == ButtonState.Pressed &&
                    menuSp.IsColliding(new Rectangle(mouse.X,mouse.Y, 1, 1)))
                )
            {
                _showMenus = false;
                IsMouseVisible = false;
                var viewPort = GetViewportRect(GraphicsDevice);
                _redPaddlePosition.X = viewPort.Right - gameRedPaddle.Width;
                if (!gameStarted)//ball is initialized, game hasn't started
                {
                    Serve(ball.MoveTo, v => ball.Vector = v);
                }
                gameStarted = true;
                return;
            }
            if (mouse.LeftButton == ButtonState.Pressed &&
                menuExit.IsColliding(new Rectangle(mouse.X, mouse.Y, 1, 1)))
            {
                this.Exit();
                return;
            }
            if(mouse.LeftButton== ButtonState.Pressed &&
                menuDungeon.IsColliding(new Rectangle(mouse.X,mouse.Y,1,1)))

            {
                if (dungeon == null)
                    dungeon = DungeonGame.InitializeDungeon(_rand);
                else
                    dungeon = null;
                
            }
        }

        private RectDungeon dungeon;
        Microsoft.Xna.Framework.Rectangle GetHitBox(Vector2 position, int width, int height)
        {
            return new Rectangle((int)position.X, (int)position.Y, width, height);
        }

        private void ProcessGame(GameTime gameTime)
        {
            if (_player1.Pause)
            {
                _showMenus = true;
                return;
            }
            if (ballCollision)
                ball.SwitchDirectionX();
            float moveFactorPerSecond = 400 * (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;


            if (_player1.Up)
            {

                _bluePaddlePosition.Y -= moveFactorPerSecond;
                if (_bluePaddlePosition.Y < GetViewportRect(GraphicsDevice).Top)
                    _bluePaddlePosition.Y = GetViewportRect(GraphicsDevice).Top;
            }

            if (_player1.Down)
            {
                _bluePaddlePosition.Y += moveFactorPerSecond;
                if (_bluePaddlePosition.Y > GetViewportRect(GraphicsDevice).Bottom)
                    _bluePaddlePosition.Y = GetViewportRect(GraphicsDevice).Bottom - gameBluePaddle.Height;
            }

            _redPaddlePosition.Y = BoundInto(0, Microsoft.Xna.Framework.Input.Mouse.GetState().Y, GetViewportRect(GraphicsDevice).Bottom,
                Microsoft.Xna.Framework.Input.Mouse.GetState().Y + gameRedPaddle.Bottom);

            if (_player1.Activate)
            {
                Fire(gameTime);
            }
            ballCollision = false;
            MoveBall(gameTime);
            var ballBox = ball.Position;

            if (ballCollision)
                ballCollision = false;
            else
                if (GetHitBox(_bluePaddlePosition, gameBluePaddle.Width, gameBluePaddle.Height).Intersects(ballBox)
                    || GetHitBox(_redPaddlePosition, gameRedPaddle.Width, gameRedPaddle.Height).Intersects(ballBox))
                {
                    ballCollision = true;
                    if (soundOn)
                        hitEffect.Play();
                }

            if(particleList.Any())
            {
                AgeParticles(gameTime,particleList);
            }
        }

        private void AgeParticles(GameTime gameTime,List<ParticleData> particleList)
        {
            IList<ParticleData> toModify = new List<ParticleData>();
            
            for (int i = 0; i < particleList.Count; i++)
            {
                var p=particleList[i];
                p.MaxAge -= gameTime.ElapsedGameTime.Milliseconds;
                toModify.Add(p);  
            }
            particleList.Clear();
           foreach (var p in toModify)
           {
               
               particleList.Remove(p);
               if (p.MaxAge > 0)
                   particleList.Add(p);
                   
           }
               
        }

        public struct ParticleData
        {
            public float BirthTime;
            public float MaxAge;
            public Vector2 OrginalPosition;
            public Vector2 Accelaration;
            public Vector2 Direction;
            public Vector2 Position;
            public float Scaling;
            public Color ModColor;
        }

        List<ParticleData> particleList = new List<ParticleData>();

        void AddExplosion(Vector2 explosionPos, int numberOfParticles, float size, float maxAge, GameTime gameTime)
        {
            for (int i = 0; i < numberOfParticles; i++)
            {
                AddExplosionParticle(explosionPos, size, maxAge, gameTime);
            }
        }

        void AddExplosionParticle(Vector2 explosionPos, float explosionSize, float maxAge, GameTime gameTime)
        {
            ParticleData particle = new ParticleData();

            particle.OrginalPosition = explosionPos;
            particle.Position = particle.OrginalPosition;

            particle.BirthTime = (float)gameTime.TotalGameTime.TotalMilliseconds;
            particle.MaxAge = maxAge;
            particle.Scaling = 0.25f;
            particle.ModColor = Color.White;

            float particleDistance = (float)_rand.NextDouble() * explosionSize;
            Vector2 displacement = new Vector2(particleDistance, 0);
            float angle = MathHelper.ToRadians(_rand.Next(360));
            displacement = Vector2.Transform(displacement, Matrix.CreateRotationZ(angle));

            particle.Direction = displacement;
            particle.Accelaration = 3.0f * particle.Direction;

            particleList.Add(particle);
        }
        void Fire(GameTime gameTime)
        {
            Console.WriteLine("firing");
            AddExplosion(_bluePaddlePosition, 4, 30.0f, 1000.0f, gameTime);
            if (ball.IntersectsY(_bluePaddlePosition.Y, gameBluePaddle.Height))
            {
                Console.WriteLine("Fire hit ball");
                ball.Vector = new Vector2(ball.Vector.X + 1, ball.Vector.Y);
                if (ball.Vector.X == 0)
                    ball.Vector = new Vector2(ball.Vector.X + 1, ball.Vector.Y);
                var pointOfImpact = new Vector2(ball.Position.X, ball.Position.Y);
                AddExplosion(pointOfImpact, 4, 30.0f, 1000.0f, gameTime);
            }
                

        }

        private void DrawExplosion()
        {
            if(particleList.Count>0)
            Console.WriteLine("drawing " + particleList.Count + " particles");
            for (int i = 0; i < particleList.Count; i++)
            {
                
                ParticleData particle = particleList[i];
                spriteBatch.Draw(explosionTexture, particle.Position, null, particle.ModColor, i, new Vector2(256, 256), particle.Scaling, SpriteEffects.None, 1);
            }
        }

        void Serve(Action<int, int> moveMethod, Action<Vector2> vectorMethod)
        {
            var viewPort = GetViewportRect(GraphicsDevice);


            _redPaddlePosition.X = viewPort.Right - gameRedPaddle.Width; //ensure player2 paddle is at proper location
            moveMethod(viewPort.Center.X, viewPort.Center.Y);

            var nextVector = new Vector2(_ballMinimumSpeed.X * _rand.NextSign(), _ballMinimumSpeed.Y * _rand.NextSign());
            vectorMethod(nextVector);


        }
        private void MoveBall(GameTime gameTime)
        {
            ball.Update(gameTime);

            int maxX = graphics.GraphicsDevice.Viewport.Width;
            int minX = 0;
            int maxY = graphics.GraphicsDevice.Viewport.Height - ball.Height;
            int minY = 0;



            if (ball.Position.X > maxX || ball.Position.X < minX)
            {
                Score(ball.Position.X);
                Serve(ball.MoveTo, v => ball.Vector = v);
            }
            if (ball.Position.Y > maxY || ball.Position.Y < minY)
            {
                ball.SwitchDirectionY();
            }

        }

        byte score1;
        byte score2;
        private Texture2D explosionTexture;
        

        void Score(int p)
        {
            if (p > 0)
                score1 += 1;
            else if (p < 0)
                score2 += 1;
            missEffect.Play();
            if (score2 > score1)
                background = cityBackground;
            else
                background = spaceBackground;
        }

        void DrawSimple(Rectangle destination, Color color)
        {
            var simple = new Texture2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
            simple.SetData<Color>(new[] { color });
            spriteBatch.Draw(simple, destination, Color.White); //white for no tinting

        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            int resolutionWidth = graphics.GraphicsDevice.Viewport.Width;
            int resolutionHeight = graphics.GraphicsDevice.Viewport.Height;
            if (!drawDebug)
                //for a repeating texture with scrolling if any
                for (int x = 0; x <= resolutionWidth / background.Width; x++)
                    for (int y = 0; y <= resolutionHeight / background.Height; y++)
                    {
                        Vector2 position = new Vector2(x * background.Width,
                                                       y * background.Height +
                                                       ((int)scrollPosition) % background.Height);
                        //GetViewportRect(graphics.GraphicsDevice)
                        spriteBatch.Draw(background, position, ballCollision ? Color.Red : Color.White);

                        //RenderSprite(backgroundTexture, x, y,backgroundTexture.Bounds ,Color.LightGray);


                        //spriteBatch.Draw()


                    }

            if (gameStarted)
            {
                DrawExplosion();
                DrawPaddles();
            }
                

            if (_showMenus)
            {
                DrawMenus();
            }
            else
            {
                if (gameStarted)
                {
                   
                    var position = ball.Position;
                    var pTexture = ball.PositionOnTexture;

                    spriteBatch.Draw(ball.Texture, position, pTexture, ballCollision ? Color.Red : Color.White);
                    spriteBatch.DrawString(font, score1.ToString(), new Vector2(gameBluePaddle.Width, 0), Color.Blue);
                    spriteBatch.DrawString(font, score2.ToString(), new Vector2(_redPaddlePosition.X - gameRedPaddle.Width, 0), Color.Red);
                    
                }
            }
            if (dungeon != null)
                spriteBatch.DrawString(font, dungeon.ToString(), new Vector2(), Color.White);
            spriteBatch.End();

            base.Draw(gameTime);
        }

        private void DrawPaddles()
        {

            if (drawDebug)
                DrawSimple(new Rectangle((int)_redPaddlePosition.X, (int)_redPaddlePosition.Y, gameRedPaddle.Width, gameRedPaddle.Height).Outset(), Color.White);

            RenderWithScreenBounds(gameTexture, _redPaddlePosition, gameRedPaddle, Color.Red);

            RenderWithScreenBounds(gameTexture, _bluePaddlePosition, gameBluePaddle, Color.Blue);
        }
        public void InitializeMenus(int nextY, int gap, params GameObjects.GameObject[] objects)
        {
            var viewPort = GetViewportRect(GraphicsDevice);
            Func<int, int> centerFunc = width => (viewPort.Width - width) / 2;
            //menuSp.MoveTo(centerFunc(menuSp.Width),nextY);
            foreach (var item in objects)
            {
                item.MoveTo(centerFunc(item.Width), nextY);
                nextY += gap + item.Height;
            }
        }
        public void DrawMenus()
        {
            var menuGap = 40;

            var viewPort = GetViewportRect(GraphicsDevice);
            var nextY = viewPort.Y + menuGap;
            Func<int, int> centerFunc = width => (viewPort.Width - width) / 2;
            if (drawDebug)
                DrawSimple(viewPort, Color.Yellow);
            RenderSprite(menuTexture, centerFunc(xnaPongLogo.Width), nextY, xnaPongLogo, Color.White);
            nextY += xnaPongLogo.Height + menuGap;

            var items = new[] { menuSp, menuMp, menuDungeon, menuExit };
            var colors = new[] { Color.Blue, Color.Red, Color.Gray, Color.DarkRed };
            var hColor = new[] { Color.White, Color.White, Color.Violet, Color.Brown};
            var state = Microsoft.Xna.Framework.Input.Mouse.GetState();
            Func<GameObjects.GameObject, bool> isHover = g =>
                                                                   g.Position.Contains(state.X,state.Y);
            for (int i = 0; i < items.Length; i++)
            {
                var current = items[i];
                current.MoveTo(centerFunc(current.Width), nextY);
                var hovering = isHover(current);
                RenderSprite(current.Texture, centerFunc(current.Width), nextY,
                             current.PositionOnTexture, hovering ? hColor[i] : colors[i]);
                nextY += menuGap + current.Height;
            }
            var dHovering = isHover(menuDungeon);
            spriteBatch.DrawString(font, "Generate Dungeon", new Vector2(centerFunc(0), menuDungeon.Position.Y),dHovering?Color.Violet: Color.White);
           
            //RenderSprite(menuTexture, centerFunc(menuMultiPlayer.Width), nextY,
            //menuMultiPlayer, Color.Gray);
            //nextY += xnaPongLogo.Height + menuGap;
            //nextY += menuMultiPlayer.Height + menuGap;

        }

        public Rectangle GetViewportRect(GraphicsDevice device)
        {
            return new Rectangle(0, 0, device.Viewport.Width, device.Viewport.Height);
        }

        public void RenderSprite(Texture2D texture, int x, int y, Rectangle source, Color color)
        {

            spriteBatch.Draw(texture, new Vector2(x, y), source, color);

        }

        static float BoundInto(float min, float itemTop, float max, float bottom)
        {
            if (itemTop < min)
                return min;
            if (bottom > max)
                return max;

            return itemTop;
        }

        public void RenderWithScreenBounds(Texture2D texture, Vector2 position, Rectangle source, Color color)
        {
            var viewPort = GetViewportRect(graphics.GraphicsDevice);
            var x = BoundInto(viewPort.Left, position.X, viewPort.Right, position.X + source.Width);
            var y = BoundInto(viewPort.Top, position.Y, viewPort.Bottom - source.Height, position.Y);


            spriteBatch.Draw(texture, new Vector2(x, y), source, color, 0.0f, new Vector2(0), 1.0f, SpriteEffects.FlipHorizontally, 0);
        }
    }
}
